/*
** Copyright (c) 2020 rxi
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to
** deal in the Software without restriction, including without limitation the
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
** sell copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
** IN THE SOFTWARE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
** Copyright (c) 2020 rxi
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the MIT license. See `microui.c` for details.
*/

#ifndef MICROUI_H
#define MICROUI_H
namespace mu {
#define VERSION "2.01"

#define COMMANDLIST_SIZE     (256 * 1024)
#define ROOTLIST_SIZE        32
#define CONTAINERSTACK_SIZE  32
#define CLIPSTACK_SIZE       32
#define IDSTACK_SIZE         32
#define LAYOUTSTACK_SIZE     16
#define CONTAINERPOOL_SIZE   48
#define TREENODEPOOL_SIZE    48
#define MAX_WIDTHS           16
#define REAL                 float
#define REAL_FMT             "%.3g"
#define SLIDER_FMT           "%.2f"
#define MAX_FMT              127

#define stack(T, n)          struct { int idx; T items[n]; }
#define min(a, b)            ((a) < (b) ? (a) : (b))
#define max(a, b)            ((a) > (b) ? (a) : (b))
#define clamp(x, a, b)       min(b, max(a, x))

enum {
  CLIP_PART = 1,
  CLIP_ALL
};

enum {
  COMMAND_JUMP = 1,
  COMMAND_CLIP,
  COMMAND_RECT,
  COMMAND_TEXT,
  COMMAND_ICON,
  COMMAND_MAX
};

enum {
  COLOR_TEXT,
  COLOR_BORDER,
  COLOR_WINDOWBG,
  COLOR_TITLEBG,
  COLOR_TITLETEXT,
  COLOR_PANELBG,
  COLOR_BUTTON,
  COLOR_BUTTONHOVER,
  COLOR_BUTTONFOCUS,
  COLOR_BASE,
  COLOR_BASEHOVER,
  COLOR_BASEFOCUS,
  COLOR_SCROLLBASE,
  COLOR_SCROLLTHUMB,
  COLOR_MAX
};

enum {
  ICON_CLOSE = 1,
  ICON_CHECK,
  ICON_COLLAPSED,
  ICON_EXPANDED,
  ICON_MAX
};

enum {
  RES_ACTIVE       = (1 << 0),
  RES_SUBMIT       = (1 << 1),
  RES_CHANGE       = (1 << 2)
};

enum {
  OPT_ALIGNCENTER  = (1 << 0),
  OPT_ALIGNRIGHT   = (1 << 1),
  OPT_NOINTERACT   = (1 << 2),
  OPT_NOFRAME      = (1 << 3),
  OPT_NORESIZE     = (1 << 4),
  OPT_NOSCROLL     = (1 << 5),
  OPT_NOCLOSE      = (1 << 6),
  OPT_NOTITLE      = (1 << 7),
  OPT_HOLDFOCUS    = (1 << 8),
  OPT_AUTOSIZE     = (1 << 9),
  OPT_POPUP        = (1 << 10),
  OPT_CLOSED       = (1 << 11),
  OPT_EXPANDED     = (1 << 12)
};

enum {
  MOUSE_LEFT       = (1 << 0),
  MOUSE_RIGHT      = (1 << 1),
  MOUSE_MIDDLE     = (1 << 2)
};

enum {
  KEY_SHIFT        = (1 << 0),
  KEY_CTRL         = (1 << 1),
  KEY_ALT          = (1 << 2),
  KEY_BACKSPACE    = (1 << 3),
  KEY_RETURN       = (1 << 4)
};


typedef struct Context Context;
typedef unsigned Id;
typedef REAL Real;
typedef void* Font;

typedef struct { int x, y; } Vec2;
typedef struct { int x, y, w, h; } Rect;
typedef struct { unsigned char r, g, b, a; } Color;
typedef struct { Id id; int last_update; } PoolItem;

typedef struct { int type, size; } BaseCommand;
typedef struct { BaseCommand base; void *dst; } JumpCommand;
typedef struct { BaseCommand base; Rect rect; } ClipCommand;
typedef struct { BaseCommand base; Rect rect; Color color; } RectCommand;
typedef struct { BaseCommand base; Font font; Vec2 pos; Color color; char str[1]; } TextCommand;
typedef struct { BaseCommand base; Rect rect; int id; Color color; } IconCommand;

typedef union {
  int type;
  BaseCommand base;
  JumpCommand jump;
  ClipCommand clip;
  RectCommand rect;
  TextCommand text;
  IconCommand icon;
} Command;

typedef struct {
  Rect body;
  Rect next;
  Vec2 position;
  Vec2 size;
  Vec2 max;
  int widths[MAX_WIDTHS];
  int items;
  int item_index;
  int next_row;
  int next_type;
  int indent;
} Layout;

typedef struct {
  Command *head, *tail;
  Rect rect;
  Rect body;
  Vec2 content_size;
  Vec2 scroll;
  int zindex;
  int open;
} Container;

typedef struct {
  Font font;
  Vec2 size;
  int padding;
  int spacing;
  int indent;
  int title_height;
  int scrollbar_size;
  int thumb_size;
  Color colors[COLOR_MAX];
} Style;

struct Context {
  /* callbacks */
  int (*text_width)(Font font, const char *str, int len);
  int (*text_height)(Font font);
  void (*draw_frame)(Context *ctx, Rect rect, int colorid);
  /* core state */
  Style _style;
  Style *style;
  Id hover;
  Id focus;
  Id last_id;
  Rect last_rect;
  int last_zindex;
  int updated_focus;
  int frame;
  Container *hover_root;
  Container *next_hover_root;
  Container *scroll_target;
  char number_edit_buf[MAX_FMT];
  Id number_edit;
  /* stacks */
  stack(char, COMMANDLIST_SIZE) command_list;
  stack(Container*, ROOTLIST_SIZE) root_list;
  stack(Container*, CONTAINERSTACK_SIZE) container_stack;
  stack(Rect, CLIPSTACK_SIZE) clip_stack;
  stack(Id, IDSTACK_SIZE) id_stack;
  stack(Layout, LAYOUTSTACK_SIZE) layout_stack;
  /* retained state pools */
  PoolItem container_pool[CONTAINERPOOL_SIZE];
  Container containers[CONTAINERPOOL_SIZE];
  PoolItem treenode_pool[TREENODEPOOL_SIZE];
  /* input state */
  Vec2 mouse_pos;
  Vec2 last_mouse_pos;
  Vec2 mouse_delta;
  Vec2 scroll_delta;
  int mouse_down;
  int mouse_pressed;
  int key_down;
  int key_pressed;
  char input_text[32];
};


Vec2 vec2(int x, int y);
Rect rRect(int x, int y, int w, int h);
Color color(int r, int g, int b, int a);

void init(Context *ctx);
void begin(Context *ctx);
void end(Context *ctx);
void set_focus(Context *ctx, Id id);
Id get_id(Context *ctx, const void *data, int size);
void push_id(Context *ctx, const void *data, int size);
void pop_id(Context *ctx);
void push_clip_rect(Context *ctx, Rect rect);
void pop_clip_rect(Context *ctx);
Rect get_clip_rect(Context *ctx);
int check_clip(Context *ctx, Rect r);
Container* get_current_container(Context *ctx);
// Container* get_container(Context *ctx, const char *name);
void bring_to_front(Context *ctx, Container *cnt);

int pool_init(Context *ctx, PoolItem *items, int len, Id id);
int pool_get(Context *ctx, PoolItem *items, int len, Id id);
void pool_update(Context *ctx, PoolItem *items, int idx);

void input_mousemove(Context *ctx, int x, int y);
void input_mousedown(Context *ctx, int x, int y, int btn);
void input_mouseup(Context *ctx, int x, int y, int btn);
void input_scroll(Context *ctx, int x, int y);
void input_keydown(Context *ctx, int key);
void input_keyup(Context *ctx, int key);
void input_text(Context *ctx, const char *text);

Command* push_command(Context *ctx, int type, int size);
int next_command(Context *ctx, Command **cmd);
void set_clip(Context *ctx, Rect rect);
void draw_rect(Context *ctx, Rect rect, Color color);
void draw_box(Context *ctx, Rect rect, Color color);
void draw_text(Context *ctx, Font font, const char *str, int len, Vec2 pos, Color color);
void draw_icon(Context *ctx, int id, Rect rect, Color color);

void layout_row(Context *ctx, int items, const int *widths, int height);
void layout_width(Context *ctx, int width);
void layout_height(Context *ctx, int height);
void layout_begin_column(Context *ctx);
void layout_end_column(Context *ctx);
void layout_set_next(Context *ctx, Rect r, int relative);
Rect layout_next(Context *ctx);

void draw_control_frame(Context *ctx, Id id, Rect rect, int colorid, int opt);
void draw_control_text(Context *ctx, const char *str, Rect rect, int colorid, int opt);
int mouse_over(Context *ctx, Rect rect);
void update_control(Context *ctx, Id id, Rect rect, int opt);

#define button(ctx, label)             button_ex(ctx, label, 0, OPT_ALIGNCENTER)
#define textbox(ctx, buf, bufsz)       textbox_ex(ctx, buf, bufsz, 0)
#define slider(ctx, value, lo, hi)     slider_ex(ctx, value, lo, hi, 0, SLIDER_FMT, OPT_ALIGNCENTER)
#define number(ctx, value, step)       number_ex(ctx, value, step, SLIDER_FMT, OPT_ALIGNCENTER)
// #define header(ctx, label)             header_ex(ctx, label, 0)
#define begin_treenode(ctx, label)     begin_treenode_ex(ctx, label, 0)
#define begin_window(ctx, title, rect) begin_window_ex(ctx, title, rect, 0)
#define begin_panel(ctx, name)         begin_panel_ex(ctx, name, 0)

void text(Context *ctx, const char *text);
void label(Context *ctx, const char *text);
int button_ex(Context *ctx, const char *label, int icon, int opt);
int checkbox(Context *ctx, const char *label, int *state);
int textbox_raw(Context *ctx, char *buf, int bufsz, Id id, Rect r, int opt);
int textbox_ex(Context *ctx, char *buf, int bufsz, int opt);
int slider_ex(Context *ctx, Real *value, Real low, Real high, Real step, const char *fmt, int opt);
int number_ex(Context *ctx, Real *value, Real step, const char *fmt, int opt);
int header_ex(Context *ctx, const char *label, int opt);
int begin_treenode_ex(Context *ctx, const char *label, int opt);
void end_treenode(Context *ctx);
int begin_window_ex(Context *ctx, const char *title, Rect rect, int opt);
void end_window(Context *ctx);
void open_popup(Context *ctx, const char *name);
int begin_popup(Context *ctx, const char *name);
void end_popup(Context *ctx);
void begin_panel_ex(Context *ctx, const char *name, int opt);
void end_panel(Context *ctx);

}

#endif
namespace mu {
#define unused(x) ((void) (x))

#define expect(x) do {                                               \
		if (!(x)) {                                                      \
			fprintf(stderr, "Fatal error: %s:%d: assertion '%s' failed\n", \
				__FILE__, __LINE__, #x);                                     \
			abort();                                                       \
		}                                                                \
	} while (0)

#define push(stk, val) do {                                                 \
		expect((stk).idx < (int) (sizeof((stk).items) / sizeof(*(stk).items))); \
		(stk).items[(stk).idx] = (val);                                         \
		(stk).idx++; /* incremented after incase `val` uses this value */       \
	} while (0)

#define pop(stk) do {      \
		expect((stk).idx > 0); \
		(stk).idx--;           \
	} while (0)



static Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 };

static Style default_style = {
	/* font | size | padding | spacing | indent */
	NULL, { 68, 10 }, 5, 4, 24,
	/* title_height | scrollbar_size | thumb_size */
	24, 12, 8,
	{
		{ 230, 230, 230, 255 }, /* COLOR_TEXT */
		{ 25,  25,  25,  255 }, /* COLOR_BORDER */
		{ 50,  50,  50,  255 }, /* COLOR_WINDOWBG */
		{ 25,  25,  25,  255 }, /* COLOR_TITLEBG */
		{ 240, 240, 240, 255 }, /* COLOR_TITLETEXT */
		{ 0,   0,   0,   0   }, /* COLOR_PANELBG */
		{ 75,  75,  75,  255 }, /* COLOR_BUTTON */
		{ 95,  95,  95,  255 }, /* COLOR_BUTTONHOVER */
		{ 115, 115, 115, 255 }, /* COLOR_BUTTONFOCUS */
		{ 30,  30,  30,  255 }, /* COLOR_BASE */
		{ 35,  35,  35,  255 }, /* COLOR_BASEHOVER */
		{ 40,  40,  40,  255 }, /* COLOR_BASEFOCUS */
		{ 43,  43,  43,  255 }, /* COLOR_SCROLLBASE */
		{ 30,  30,  30,  255 }  /* COLOR_SCROLLTHUMB */
	}
};


Vec2 vec2(int x, int y) {
	Vec2 res;
	res.x = x; res.y = y;
	return res;
}


Rect rRect(int x, int y, int w, int h) {
	Rect res;
	res.x = x; res.y = y; res.w = w; res.h = h;
	return res;
}


Color color(int r, int g, int b, int a) {
	Color res;
	res.r = r; res.g = g; res.b = b; res.a = a;
	return res;
}


static Rect expand_rect(Rect rect, int n) {
	return rRect(rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2);
}


static Rect intersect_rects(Rect r1, Rect r2) {
	int x1 = max(r1.x, r2.x);
	int y1 = max(r1.y, r2.y);
	int x2 = min(r1.x + r1.w, r2.x + r2.w);
	int y2 = min(r1.y + r1.h, r2.y + r2.h);
	if (x2 < x1) { x2 = x1; }
	if (y2 < y1) { y2 = y1; }
	return rRect(x1, y1, x2 - x1, y2 - y1);
}


static int rect_overlaps_vec2(Rect r, Vec2 p) {
	return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h;
}


static void draw_frame(Context *ctx, Rect rect, int colorid) {
	draw_rect(ctx, rect, ctx->style->colors[colorid]);
	if (colorid == COLOR_SCROLLBASE  ||
			colorid == COLOR_SCROLLTHUMB ||
			colorid == COLOR_TITLEBG) { return; }
	/* draw border */
	if (ctx->style->colors[COLOR_BORDER].a) {
		draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[COLOR_BORDER]);
	}
}


void init(Context *ctx) {
	memset(ctx, 0, sizeof(*ctx));
	ctx->draw_frame = draw_frame;
	ctx->_style = default_style;
	ctx->style = &ctx->_style;
}


void begin(Context *ctx) {
	expect(ctx->text_width && ctx->text_height);
	ctx->command_list.idx = 0;
	ctx->root_list.idx = 0;
	ctx->scroll_target = NULL;
	ctx->hover_root = ctx->next_hover_root;
	ctx->next_hover_root = NULL;
	ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x;
	ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y;
	ctx->frame++;
}


static int compare_zindex(const void *a, const void *b) {
	return (*(Container**) a)->zindex - (*(Container**) b)->zindex;
}


void end(Context *ctx) {
	int i, n;
	/* check stacks */
	expect(ctx->container_stack.idx == 0);
	expect(ctx->clip_stack.idx      == 0);
	expect(ctx->id_stack.idx        == 0);
	expect(ctx->layout_stack.idx    == 0);

	/* handle scroll input */
	if (ctx->scroll_target) {
		ctx->scroll_target->scroll.x += ctx->scroll_delta.x;
		ctx->scroll_target->scroll.y += ctx->scroll_delta.y;
	}

	/* unset focus if focus id was not touched this frame */
	if (!ctx->updated_focus) { ctx->focus = 0; }
	ctx->updated_focus = 0;

	/* bring hover root to front if mouse was pressed */
	if (ctx->mouse_pressed && ctx->next_hover_root &&
			ctx->next_hover_root->zindex < ctx->last_zindex &&
			ctx->next_hover_root->zindex >= 0
	) {
		bring_to_front(ctx, ctx->next_hover_root);
	}

	/* reset input state */
	ctx->key_pressed = 0;
	ctx->input_text[0] = '\0';
	ctx->mouse_pressed = 0;
	ctx->scroll_delta = vec2(0, 0);
	ctx->last_mouse_pos = ctx->mouse_pos;

	/* sort root containers by zindex */
	n = ctx->root_list.idx;
	qsort(ctx->root_list.items, n, sizeof(Container*), compare_zindex);

	/* set root container jump commands */
	for (i = 0; i < n; i++) {
		Container *cnt = ctx->root_list.items[i];
		/* if this is the first container then make the first command jump to it.
		** otherwise set the previous container's tail to jump to this one */
		if (i == 0) {
			Command *cmd = (Command*) ctx->command_list.items;
			cmd->jump.dst = (char*) cnt->head + sizeof(JumpCommand);
		} else {
			Container *prev = ctx->root_list.items[i - 1];
			prev->tail->jump.dst = (char*) cnt->head + sizeof(JumpCommand);
		}
		/* make the last container's tail jump to the end of command list */
		if (i == n - 1) {
			cnt->tail->jump.dst = ctx->command_list.items + ctx->command_list.idx;
		}
	}
}


void set_focus(Context *ctx, Id id) {
	ctx->focus = id;
	ctx->updated_focus = 1;
}


/* 32bit fnv-1a hash */
#define HASH_INITIAL 2166136261

static void hash(Id *hash, const void *data, int size) {
	const unsigned char *p = (unsigned char*)data;
	while (size--) {
		*hash = (*hash ^ *p++) * 16777619;
	}
}


Id get_id(Context *ctx, const void *data, int size) {
	int idx = ctx->id_stack.idx;
	Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL;
	hash(&res, data, size);
	ctx->last_id = res;
	return res;
}


void push_id(Context *ctx, const void *data, int size) {
	push(ctx->id_stack, get_id(ctx, data, size));
}


void pop_id(Context *ctx) {
	pop(ctx->id_stack);
}


void push_clip_rect(Context *ctx, Rect rect) {
	Rect last = get_clip_rect(ctx);
	push(ctx->clip_stack, intersect_rects(rect, last));
}


void pop_clip_rect(Context *ctx) {
	pop(ctx->clip_stack);
}


Rect get_clip_rect(Context *ctx) {
	expect(ctx->clip_stack.idx > 0);
	return ctx->clip_stack.items[ctx->clip_stack.idx - 1];
}


int check_clip(Context *ctx, Rect r) {
	Rect cr = get_clip_rect(ctx);
	if (r.x > cr.x + cr.w || r.x + r.w < cr.x ||
			r.y > cr.y + cr.h || r.y + r.h < cr.y   ) { return CLIP_ALL; }
	if (r.x >= cr.x && r.x + r.w <= cr.x + cr.w &&
			r.y >= cr.y && r.y + r.h <= cr.y + cr.h ) { return 0; }
	return CLIP_PART;
}


static void push_layout(Context *ctx, Rect body, Vec2 scroll) {
	Layout layout;
	int width = 0;
	memset(&layout, 0, sizeof(layout));
	layout.body = rRect(body.x - scroll.x, body.y - scroll.y, body.w, body.h);
	layout.max = vec2(-0x1000000, -0x1000000);
	push(ctx->layout_stack, layout);
	layout_row(ctx, 1, &width, 0);
}


static Layout* get_layout(Context *ctx) {
	return &ctx->layout_stack.items[ctx->layout_stack.idx - 1];
}


static void pop_container(Context *ctx) {
	Container *cnt = get_current_container(ctx);
	Layout *layout = get_layout(ctx);
	cnt->content_size.x = layout->max.x - layout->body.x;
	cnt->content_size.y = layout->max.y - layout->body.y;
	/* pop container, layout and id */
	pop(ctx->container_stack);
	pop(ctx->layout_stack);
	pop_id(ctx);
}


Container* get_current_container(Context *ctx) {
	expect(ctx->container_stack.idx > 0);
	return ctx->container_stack.items[ ctx->container_stack.idx - 1 ];
}


static Container* get_container(Context *ctx, Id id, int opt) {
	Container *cnt;
	/* try to get existing container from pool */
	int idx = pool_get(ctx, ctx->container_pool, CONTAINERPOOL_SIZE, id);
	if (idx >= 0) {
		if (ctx->containers[idx].open || ~opt & OPT_CLOSED) {
			pool_update(ctx, ctx->container_pool, idx);
		}
		return &ctx->containers[idx];
	}
	if (opt & OPT_CLOSED) { return NULL; }
	/* container not found in pool: init new container */
	idx = pool_init(ctx, ctx->container_pool, CONTAINERPOOL_SIZE, id);
	cnt = &ctx->containers[idx];
	memset(cnt, 0, sizeof(*cnt));
	cnt->open = 1;
	bring_to_front(ctx, cnt);
	return cnt;
}


void bring_to_front(Context *ctx, Container *cnt) {
	cnt->zindex = ++ctx->last_zindex;
}


/*============================================================================
** pool
**============================================================================*/

int pool_init(Context *ctx, PoolItem *items, int len, Id id) {
	int i, n = -1, f = ctx->frame;
	for (i = 0; i < len; i++) {
		if (items[i].last_update < f) {
			f = items[i].last_update;
			n = i;
		}
	}
	expect(n > -1);
	items[n].id = id;
	pool_update(ctx, items, n);
	return n;
}


int pool_get(Context *ctx, PoolItem *items, int len, Id id) {
	int i;
	unused(ctx);
	for (i = 0; i < len; i++) {
		if (items[i].id == id) { return i; }
	}
	return -1;
}


void pool_update(Context *ctx, PoolItem *items, int idx) {
	items[idx].last_update = ctx->frame;
}


/*============================================================================
** input handlers
**============================================================================*/

void input_mousemove(Context *ctx, int x, int y) {
	ctx->mouse_pos = vec2(x, y);
}


void input_mousedown(Context *ctx, int x, int y, int btn) {
	input_mousemove(ctx, x, y);
	ctx->mouse_down |= btn;
	ctx->mouse_pressed |= btn;
}


void input_mouseup(Context *ctx, int x, int y, int btn) {
	input_mousemove(ctx, x, y);
	ctx->mouse_down &= ~btn;
}


void input_scroll(Context *ctx, int x, int y) {
	ctx->scroll_delta.x += x;
	ctx->scroll_delta.y += y;
}


void input_keydown(Context *ctx, int key) {
	ctx->key_pressed |= key;
	ctx->key_down |= key;
}


void input_keyup(Context *ctx, int key) {
	ctx->key_down &= ~key;
}


void input_text(Context *ctx, const char *text) {
	int len = strlen(ctx->input_text);
	int size = strlen(text) + 1;
	expect(len + size <= (int) sizeof(ctx->input_text));
	memcpy(ctx->input_text + len, text, size);
}


/*============================================================================
** commandlist
**============================================================================*/

Command* push_command(Context *ctx, int type, int size) {
	Command *cmd = (Command*) (ctx->command_list.items + ctx->command_list.idx);
	expect(ctx->command_list.idx + size < COMMANDLIST_SIZE);
	cmd->base.type = type;
	cmd->base.size = size;
	ctx->command_list.idx += size;
	return cmd;
}


int next_command(Context *ctx, Command **cmd) {
	if (*cmd) {
		*cmd = (Command*) (((char*) *cmd) + (*cmd)->base.size);
	} else {
		*cmd = (Command*) ctx->command_list.items;
	}
	while ((char*) *cmd != ctx->command_list.items + ctx->command_list.idx) {
		if ((*cmd)->type != COMMAND_JUMP) { return 1; }
		*cmd = (Command*)(*cmd)->jump.dst;
	}
	return 0;
}


static Command* push_jump(Context *ctx, Command *dst) {
	Command *cmd;
	cmd = push_command(ctx, COMMAND_JUMP, sizeof(JumpCommand));
	cmd->jump.dst = dst;
	return cmd;
}


void set_clip(Context *ctx, Rect rect) {
	Command *cmd;
	cmd = push_command(ctx, COMMAND_CLIP, sizeof(ClipCommand));
	cmd->clip.rect = rect;
}


void draw_rect(Context *ctx, Rect rect, Color color) {
	Command *cmd;
	rect = intersect_rects(rect, get_clip_rect(ctx));
	if (rect.w > 0 && rect.h > 0) {
		cmd = push_command(ctx, COMMAND_RECT, sizeof(RectCommand));
		cmd->rect.rect = rect;
		cmd->rect.color = color;
	}
}


void draw_box(Context *ctx, Rect rect, Color color) {
	draw_rect(ctx, rRect(rect.x + 1, rect.y, rect.w - 2, 1), color);
	draw_rect(ctx, rRect(rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1), color);
	draw_rect(ctx, rRect(rect.x, rect.y, 1, rect.h), color);
	draw_rect(ctx, rRect(rect.x + rect.w - 1, rect.y, 1, rect.h), color);
}


void draw_text(Context *ctx, Font font, const char *str, int len,
	Vec2 pos, Color color)
{
	Command *cmd;
	Rect rect = rRect(
		pos.x, pos.y, ctx->text_width(font, str, len), ctx->text_height(font));
	int clipped = check_clip(ctx, rect);
	if (clipped == CLIP_ALL ) { return; }
	if (clipped == CLIP_PART) { set_clip(ctx, get_clip_rect(ctx)); }
	/* add command */
	if (len < 0) { len = strlen(str); }
	cmd = push_command(ctx, COMMAND_TEXT, sizeof(TextCommand) + len);
	memcpy(cmd->text.str, str, len);
	cmd->text.str[len] = '\0';
	cmd->text.pos = pos;
	cmd->text.color = color;
	cmd->text.font = font;
	/* reset clipping if it was set */
	if (clipped) { set_clip(ctx, unclipped_rect); }
}


void draw_icon(Context *ctx, int id, Rect rect, Color color) {
	Command *cmd;
	/* do clip command if the rect isn't fully contained within the cliprect */
	int clipped = check_clip(ctx, rect);
	if (clipped == CLIP_ALL ) { return; }
	if (clipped == CLIP_PART) { set_clip(ctx, get_clip_rect(ctx)); }
	/* do icon command */
	cmd = push_command(ctx, COMMAND_ICON, sizeof(IconCommand));
	cmd->icon.id = id;
	cmd->icon.rect = rect;
	cmd->icon.color = color;
	/* reset clipping if it was set */
	if (clipped) { set_clip(ctx, unclipped_rect); }
}


/*============================================================================
** layout
**============================================================================*/

enum { RELATIVE = 1, ABSOLUTE = 2 };


void layout_begin_column(Context *ctx) {
	push_layout(ctx, layout_next(ctx), vec2(0, 0));
}


void layout_end_column(Context *ctx) {
	Layout *a, *b;
	b = get_layout(ctx);
	pop(ctx->layout_stack);
	/* inherit position/next_row/max from child layout if they are greater */
	a = get_layout(ctx);
	a->position.x = max(a->position.x, b->position.x + b->body.x - a->body.x);
	a->next_row = max(a->next_row, b->next_row + b->body.y - a->body.y);
	a->max.x = max(a->max.x, b->max.x);
	a->max.y = max(a->max.y, b->max.y);
}


void layout_row(Context *ctx, int items, const int *widths, int height) {
	Layout *layout = get_layout(ctx);
	if (widths) {
		expect(items <= MAX_WIDTHS);
		memcpy(layout->widths, widths, items * sizeof(widths[0]));
	}
	layout->items = items;
	layout->position = vec2(layout->indent, layout->next_row);
	layout->size.y = height;
	layout->item_index = 0;
}


void layout_width(Context *ctx, int width) {
	get_layout(ctx)->size.x = width;
}


void layout_height(Context *ctx, int height) {
	get_layout(ctx)->size.y = height;
}


void layout_set_next(Context *ctx, Rect r, int relative) {
	Layout *layout = get_layout(ctx);
	layout->next = r;
	layout->next_type = relative ? RELATIVE : ABSOLUTE;
}


Rect layout_next(Context *ctx) {
	Layout *layout = get_layout(ctx);
	Style *style = ctx->style;
	Rect res;

	if (layout->next_type) {
		/* handle rect set by `layout_set_next` */
		int type = layout->next_type;
		layout->next_type = 0;
		res = layout->next;
		if (type == ABSOLUTE) { return (ctx->last_rect = res); }

	} else {
		/* handle next row */
		if (layout->item_index == layout->items) {
			layout_row(ctx, layout->items, NULL, layout->size.y);
		}

		/* position */
		res.x = layout->position.x;
		res.y = layout->position.y;

		/* size */
		res.w = layout->items > 0 ? layout->widths[layout->item_index] : layout->size.x;
		res.h = layout->size.y;
		if (res.w == 0) { res.w = style->size.x + style->padding * 2; }
		if (res.h == 0) { res.h = style->size.y + style->padding * 2; }
		if (res.w <  0) { res.w += layout->body.w - res.x + 1; }
		if (res.h <  0) { res.h += layout->body.h - res.y + 1; }

		layout->item_index++;
	}

	/* update position */
	layout->position.x += res.w + style->spacing;
	layout->next_row = max(layout->next_row, res.y + res.h + style->spacing);

	/* apply body offset */
	res.x += layout->body.x;
	res.y += layout->body.y;

	/* update max position */
	layout->max.x = max(layout->max.x, res.x + res.w);
	layout->max.y = max(layout->max.y, res.y + res.h);

	return (ctx->last_rect = res);
}


/*============================================================================
** controls
**============================================================================*/

static int in_hover_root(Context *ctx) {
	int i = ctx->container_stack.idx;
	while (i--) {
		if (ctx->container_stack.items[i] == ctx->hover_root) { return 1; }
		/* only root containers have their `head` field set; stop searching if we've
		** reached the current root container */
		if (ctx->container_stack.items[i]->head) { break; }
	}
	return 0;
}


void draw_control_frame(Context *ctx, Id id, Rect rect,
	int colorid, int opt)
{
	if (opt & OPT_NOFRAME) { return; }
	colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0;
	ctx->draw_frame(ctx, rect, colorid);
}


void draw_control_text(Context *ctx, const char *str, Rect rect,
	int colorid, int opt)
{
	Vec2 pos;
	Font font = ctx->style->font;
	int tw = ctx->text_width(font, str, -1);
	push_clip_rect(ctx, rect);
	pos.y = rect.y + (rect.h - ctx->text_height(font)) / 2;
	if (opt & OPT_ALIGNCENTER) {
		pos.x = rect.x + (rect.w - tw) / 2;
	} else if (opt & OPT_ALIGNRIGHT) {
		pos.x = rect.x + rect.w - tw - ctx->style->padding;
	} else {
		pos.x = rect.x + ctx->style->padding;
	}
	draw_text(ctx, font, str, -1, pos, ctx->style->colors[colorid]);
	pop_clip_rect(ctx);
}


int mouse_over(Context *ctx, Rect rect) {
	return rect_overlaps_vec2(rect, ctx->mouse_pos) &&
		rect_overlaps_vec2(get_clip_rect(ctx), ctx->mouse_pos) &&
		in_hover_root(ctx);
}


void update_control(Context *ctx, Id id, Rect rect, int opt) {
	int mouseover = mouse_over(ctx, rect);

	if (ctx->focus == id) { ctx->updated_focus = 1; }
	if (opt & OPT_NOINTERACT) { return; }
	if (mouseover && !ctx->mouse_down) { ctx->hover = id; }

	if (ctx->focus == id) {
		if (ctx->mouse_pressed && !mouseover) { set_focus(ctx, 0); }
		if (!ctx->mouse_down && ~opt & OPT_HOLDFOCUS) { set_focus(ctx, 0); }
	}

	if (ctx->hover == id) {
		if (ctx->mouse_pressed) {
			set_focus(ctx, id);
		} else if (!mouseover) {
			ctx->hover = 0;
		}
	}
}


void text(Context *ctx, const char *text) {
	const char *start, *end, *p = text;
	int width = -1;
	Font font = ctx->style->font;
	Color color = ctx->style->colors[COLOR_TEXT];
	layout_begin_column(ctx);
	layout_row(ctx, 1, &width, ctx->text_height(font));
	do {
		Rect r = layout_next(ctx);
		int w = 0;
		start = end = p;
		do {
			const char* word = p;
			while (*p && *p != ' ' && *p != '\n') { p++; }
			w += ctx->text_width(font, word, p - word);
			if (w > r.w && end != start) { break; }
			w += ctx->text_width(font, p, 1);
			end = p++;
		} while (*end && *end != '\n');
		draw_text(ctx, font, start, end - start, vec2(r.x, r.y), color);
		p = end + 1;
	} while (*end);
	layout_end_column(ctx);
}


void label(Context *ctx, const char *text) {
	draw_control_text(ctx, text, layout_next(ctx), COLOR_TEXT, 0);
}


int button_ex(Context *ctx, const char *label, int icon, int opt) {
	int res = 0;
	Id id = label ? get_id(ctx, label, strlen(label))
									 : get_id(ctx, &icon, sizeof(icon));
	Rect r = layout_next(ctx);
	update_control(ctx, id, r, opt);
	/* handle click */
	if (ctx->mouse_pressed == MOUSE_LEFT && ctx->focus == id) {
		res |= RES_SUBMIT;
	}
	/* draw */
	draw_control_frame(ctx, id, r, COLOR_BUTTON, opt);
	if (label) { draw_control_text(ctx, label, r, COLOR_TEXT, opt); }
	if (icon) { draw_icon(ctx, icon, r, ctx->style->colors[COLOR_TEXT]); }
	return res;
}


int checkbox(Context *ctx, const char *label, int *state) {
	int res = 0;
	Id id = get_id(ctx, &state, sizeof(state));
	Rect r = layout_next(ctx);
	Rect box = rRect(r.x, r.y, r.h, r.h);
	update_control(ctx, id, r, 0);
	/* handle click */
	if (ctx->mouse_pressed == MOUSE_LEFT && ctx->focus == id) {
		res |= RES_CHANGE;
		*state = !*state;
	}
	/* draw */
	draw_control_frame(ctx, id, box, COLOR_BASE, 0);
	if (*state) {
		draw_icon(ctx, ICON_CHECK, box, ctx->style->colors[COLOR_TEXT]);
	}
	r = rRect(r.x + box.w, r.y, r.w - box.w, r.h);
	draw_control_text(ctx, label, r, COLOR_TEXT, 0);
	return res;
}


int textbox_raw(Context *ctx, char *buf, int bufsz, Id id, Rect r,
	int opt)
{
	int res = 0;
	update_control(ctx, id, r, opt | OPT_HOLDFOCUS);

	if (ctx->focus == id) {
		/* handle text input */
		int len = strlen(buf);
		int n = min(bufsz - len - 1, (int) strlen(ctx->input_text));
		if (n > 0) {
			memcpy(buf + len, ctx->input_text, n);
			len += n;
			buf[len] = '\0';
			res |= RES_CHANGE;
		}
		/* handle backspace */
		if (ctx->key_pressed & KEY_BACKSPACE && len > 0) {
			/* skip utf-8 continuation bytes */
			while ((buf[--len] & 0xc0) == 0x80 && len > 0);
			buf[len] = '\0';
			res |= RES_CHANGE;
		}
		/* handle return */
		if (ctx->key_pressed & KEY_RETURN) {
			set_focus(ctx, 0);
			res |= RES_SUBMIT;
		}
	}

	/* draw */
	draw_control_frame(ctx, id, r, COLOR_BASE, opt);
	if (ctx->focus == id) {
		Color color = ctx->style->colors[COLOR_TEXT];
		Font font = ctx->style->font;
		int textw = ctx->text_width(font, buf, -1);
		int texth = ctx->text_height(font);
		int ofx = r.w - ctx->style->padding - textw - 1;
		int textx = r.x + min(ofx, ctx->style->padding);
		int texty = r.y + (r.h - texth) / 2;
		push_clip_rect(ctx, r);
		draw_text(ctx, font, buf, -1, vec2(textx, texty), color);
		draw_rect(ctx, rRect(textx + textw, texty, 1, texth), color);
		pop_clip_rect(ctx);
	} else {
		draw_control_text(ctx, buf, r, COLOR_TEXT, opt);
	}

	return res;
}


static int number_textbox(Context *ctx, Real *value, Rect r, Id id) {
	if (ctx->mouse_pressed == MOUSE_LEFT && ctx->key_down & KEY_SHIFT &&
			ctx->hover == id
	) {
		ctx->number_edit = id;
		sprintf(ctx->number_edit_buf, REAL_FMT, *value);
	}
	if (ctx->number_edit == id) {
		int res = textbox_raw(
			ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0);
		if (res & RES_SUBMIT || ctx->focus != id) {
			*value = strtod(ctx->number_edit_buf, NULL);
			ctx->number_edit = 0;
		} else {
			return 1;
		}
	}
	return 0;
}


int textbox_ex(Context *ctx, char *buf, int bufsz, int opt) {
	Id id = get_id(ctx, &buf, sizeof(buf));
	Rect r = layout_next(ctx);
	return textbox_raw(ctx, buf, bufsz, id, r, opt);
}


int slider_ex(Context *ctx, Real *value, Real low, Real high,
	Real step, const char *fmt, int opt)
{
	char buf[MAX_FMT + 1];
	Rect thumb;
	int x, w, res = 0;
	Real last = *value, v = last;
	Id id = get_id(ctx, &value, sizeof(value));
	Rect base = layout_next(ctx);

	/* handle text input mode */
	if (number_textbox(ctx, &v, base, id)) { return res; }

	/* handle normal mode */
	update_control(ctx, id, base, opt);

	/* handle input */
	if (ctx->focus == id && ctx->mouse_down == MOUSE_LEFT) {
		v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w;
		if (step) { v = (((v + step / 2) / step)) * step; }
	}
	/* clamp and store value, update res */
	*value = v = clamp(v, low, high);
	if (last != v) { res |= RES_CHANGE; }

	/* draw base */
	draw_control_frame(ctx, id, base, COLOR_BASE, opt);
	/* draw thumb */
	w = ctx->style->thumb_size;
	x = (v - low) * (base.w - w) / (high - low);
	thumb = rRect(base.x + x, base.y, w, base.h);
	draw_control_frame(ctx, id, thumb, COLOR_BUTTON, opt);
	/* draw text  */
	sprintf(buf, fmt, v);
	draw_control_text(ctx, buf, base, COLOR_TEXT, opt);

	return res;
}


int number_ex(Context *ctx, Real *value, Real step,
	const char *fmt, int opt)
{
	char buf[MAX_FMT + 1];
	int res = 0;
	Id id = get_id(ctx, &value, sizeof(value));
	Rect base = layout_next(ctx);
	Real last = *value;

	/* handle text input mode */
	if (number_textbox(ctx, value, base, id)) { return res; }

	/* handle normal mode */
	update_control(ctx, id, base, opt);

	/* handle input */
	if (ctx->focus == id && ctx->mouse_down == MOUSE_LEFT) {
		*value += ctx->mouse_delta.x * step;
	}
	/* set flag if value changed */
	if (*value != last) { res |= RES_CHANGE; }

	/* draw base */
	draw_control_frame(ctx, id, base, COLOR_BASE, opt);
	/* draw text  */
	sprintf(buf, fmt, *value);
	draw_control_text(ctx, buf, base, COLOR_TEXT, opt);

	return res;
}


static int header(Context *ctx, const char *label, int istreenode, int opt) {
	Rect r;
	int active, expanded;
	Id id = get_id(ctx, label, strlen(label));
	int idx = pool_get(ctx, ctx->treenode_pool, TREENODEPOOL_SIZE, id);
	int width = -1;
	layout_row(ctx, 1, &width, 0);

	active = (idx >= 0);
	expanded = (opt & OPT_EXPANDED) ? !active : active;
	r = layout_next(ctx);
	update_control(ctx, id, r, 0);

	/* handle click */
	active ^= (ctx->mouse_pressed == MOUSE_LEFT && ctx->focus == id);

	/* update pool ref */
	if (idx >= 0) {
		if (active) { pool_update(ctx, ctx->treenode_pool, idx); }
					 else { memset(&ctx->treenode_pool[idx], 0, sizeof(PoolItem)); }
	} else if (active) {
		pool_init(ctx, ctx->treenode_pool, TREENODEPOOL_SIZE, id);
	}

	/* draw */
	if (istreenode) {
		if (ctx->hover == id) { ctx->draw_frame(ctx, r, COLOR_BUTTONHOVER); }
	} else {
		draw_control_frame(ctx, id, r, COLOR_BUTTON, 0);
	}
	draw_icon(
		ctx, expanded ? ICON_EXPANDED : ICON_COLLAPSED,
		rRect(r.x, r.y, r.h, r.h), ctx->style->colors[COLOR_TEXT]);
	r.x += r.h - ctx->style->padding;
	r.w -= r.h - ctx->style->padding;
	draw_control_text(ctx, label, r, COLOR_TEXT, 0);

	return expanded ? RES_ACTIVE : 0;
}


int header_ex(Context *ctx, const char *label, int opt) {
	return header(ctx, label, 0, opt);
}


int begin_treenode_ex(Context *ctx, const char *label, int opt) {
	int res = header(ctx, label, 1, opt);
	if (res & RES_ACTIVE) {
		get_layout(ctx)->indent += ctx->style->indent;
		push(ctx->id_stack, ctx->last_id);
	}
	return res;
}


void end_treenode(Context *ctx) {
	get_layout(ctx)->indent -= ctx->style->indent;
	pop_id(ctx);
}


#define scrollbar(ctx, cnt, b, cs, x, y, w, h)                              \
	do {                                                                      \
		/* only add scrollbar if content size is larger than body */            \
		int maxscroll = cs.y - b->h;                                            \
																																						\
		if (maxscroll > 0 && b->h > 0) {                                        \
			Rect base, thumb;                                                  \
			Id id = get_id(ctx, "!scrollbar" #y, 11);                       \
																																						\
			/* get sizing / positioning */                                        \
			base = *b;                                                            \
			base.x = b->x + b->w;                                                 \
			base.w = ctx->style->scrollbar_size;                                  \
																																						\
			/* handle input */                                                    \
			update_control(ctx, id, base, 0);                                  \
			if (ctx->focus == id && ctx->mouse_down == MOUSE_LEFT) {           \
				cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h;                \
			}                                                                     \
			/* clamp scroll to limits */                                          \
			cnt->scroll.y = clamp(cnt->scroll.y, 0, maxscroll);                \
																																						\
			/* draw base and thumb */                                             \
			ctx->draw_frame(ctx, base, COLOR_SCROLLBASE);                      \
			thumb = base;                                                         \
			thumb.h = max(ctx->style->thumb_size, base.h * b->h / cs.y);       \
			thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll;            \
			ctx->draw_frame(ctx, thumb, COLOR_SCROLLTHUMB);                    \
																																						\
			/* set this as the scroll_target (will get scrolled on mousewheel) */ \
			/* if the mouse is over it */                                         \
			if (mouse_over(ctx, *b)) { ctx->scroll_target = cnt; }             \
		} else {                                                                \
			cnt->scroll.y = 0;                                                    \
		}                                                                       \
	} while (0)


static void scrollbars(Context *ctx, Container *cnt, Rect *body) {
	int sz = ctx->style->scrollbar_size;
	Vec2 cs = cnt->content_size;
	cs.x += ctx->style->padding * 2;
	cs.y += ctx->style->padding * 2;
	push_clip_rect(ctx, *body);
	/* resize body to make room for scrollbars */
	if (cs.y > cnt->body.h) { body->w -= sz; }
	if (cs.x > cnt->body.w) { body->h -= sz; }
	/* to create a horizontal or vertical scrollbar almost-identical code is
	** used; only the references to `x|y` `w|h` need to be switched */
	scrollbar(ctx, cnt, body, cs, x, y, w, h);
	scrollbar(ctx, cnt, body, cs, y, x, h, w);
	pop_clip_rect(ctx);
}


static void push_container_body(
	Context *ctx, Container *cnt, Rect body, int opt
) {
	if (~opt & OPT_NOSCROLL) { scrollbars(ctx, cnt, &body); }
	push_layout(ctx, expand_rect(body, -ctx->style->padding), cnt->scroll);
	cnt->body = body;
}


static void begin_root_container(Context *ctx, Container *cnt) {
	push(ctx->container_stack, cnt);
	/* push container to roots list and push head command */
	push(ctx->root_list, cnt);
	cnt->head = push_jump(ctx, NULL);
	/* set as hover root if the mouse is overlapping this container and it has a
	** higher zindex than the current hover root */
	if (rect_overlaps_vec2(cnt->rect, ctx->mouse_pos) &&
			(!ctx->next_hover_root || cnt->zindex > ctx->next_hover_root->zindex)
	) {
		ctx->next_hover_root = cnt;
	}
	/* clipping is reset here in case a root-container is made within
	** another root-containers's begin/end block; this prevents the inner
	** root-container being clipped to the outer */
	push(ctx->clip_stack, unclipped_rect);
}


static void end_root_container(Context *ctx) {
	/* push tail 'goto' jump command and set head 'skip' command. the final steps
	** on initing these are done in end() */
	Container *cnt = get_current_container(ctx);
	cnt->tail = push_jump(ctx, NULL);
	cnt->head->jump.dst = ctx->command_list.items + ctx->command_list.idx;
	/* pop base clip rect and container */
	pop_clip_rect(ctx);
	pop_container(ctx);
}


int begin_window_ex(Context *ctx, const char *title, Rect rect, int opt) {
	Rect body;
	Id id = get_id(ctx, title, strlen(title));
	Container *cnt = get_container(ctx, id, opt);
	if (!cnt || !cnt->open) { return 0; }
	push(ctx->id_stack, id);

	if (cnt->rect.w == 0) { cnt->rect = rect; }
	begin_root_container(ctx, cnt);
	rect = body = cnt->rect;

	/* draw frame */
	if (~opt & OPT_NOFRAME) {
		ctx->draw_frame(ctx, rect, COLOR_WINDOWBG);
	}

	/* do title bar */
	if (~opt & OPT_NOTITLE) {
		Rect tr = rect;
		tr.h = ctx->style->title_height;
		ctx->draw_frame(ctx, tr, COLOR_TITLEBG);

		/* do title text */
		if (~opt & OPT_NOTITLE) {
			Id id = get_id(ctx, "!title", 6);
			update_control(ctx, id, tr, opt);
			draw_control_text(ctx, title, tr, COLOR_TITLETEXT, opt);
			if (id == ctx->focus && ctx->mouse_down == MOUSE_LEFT) {
				cnt->rect.x += ctx->mouse_delta.x;
				cnt->rect.y += ctx->mouse_delta.y;
			}
			body.y += tr.h;
			body.h -= tr.h;
		}

		/* do `close` button */
		if (~opt & OPT_NOCLOSE) {
			Id id = get_id(ctx, "!close", 6);
			Rect r = rRect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h);
			tr.w -= r.w;
			draw_icon(ctx, ICON_CLOSE, r, ctx->style->colors[COLOR_TITLETEXT]);
			update_control(ctx, id, r, opt);
			if (ctx->mouse_pressed == MOUSE_LEFT && id == ctx->focus) {
				cnt->open = 0;
			}
		}
	}

	push_container_body(ctx, cnt, body, opt);

	/* do `resize` handle */
	if (~opt & OPT_NORESIZE) {
		int sz = ctx->style->title_height;
		Id id = get_id(ctx, "!resize", 7);
		Rect r = rRect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz);
		update_control(ctx, id, r, opt);
		if (id == ctx->focus && ctx->mouse_down == MOUSE_LEFT) {
			cnt->rect.w = max(96, cnt->rect.w + ctx->mouse_delta.x);
			cnt->rect.h = max(64, cnt->rect.h + ctx->mouse_delta.y);
		}
	}

	/* resize to content size */
	if (opt & OPT_AUTOSIZE) {
		Rect r = get_layout(ctx)->body;
		cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w);
		cnt->rect.h = cnt->content_size.y + (cnt->rect.h - r.h);
	}

	/* close if this is a popup window and elsewhere was clicked */
	if (opt & OPT_POPUP && ctx->mouse_pressed && ctx->hover_root != cnt) {
		cnt->open = 0;
	}

	push_clip_rect(ctx, cnt->body);
	return RES_ACTIVE;
}


void end_window(Context *ctx) {
	pop_clip_rect(ctx);
	end_root_container(ctx);
}


void open_popup(Context *ctx, const char *name) {
	Container *cnt = get_container(ctx, get_id(ctx, name, strlen(name)), 0);;
	/* set as hover root so popup isn't closed in begin_window_ex()  */
	ctx->hover_root = ctx->next_hover_root = cnt;
	/* position at mouse cursor, open and bring-to-front */
	cnt->rect = rRect(ctx->mouse_pos.x, ctx->mouse_pos.y, 1, 1);
	cnt->open = 1;
	bring_to_front(ctx, cnt);
}


int begin_popup(Context *ctx, const char *name) {
	int opt = OPT_POPUP | OPT_AUTOSIZE | OPT_NORESIZE |
						OPT_NOSCROLL | OPT_NOTITLE | OPT_CLOSED;
	return begin_window_ex(ctx, name, rRect(0, 0, 0, 0), opt);
}


void end_popup(Context *ctx) {
	end_window(ctx);
}


void begin_panel_ex(Context *ctx, const char *name, int opt) {
	Container *cnt;
	push_id(ctx, name, strlen(name));
	cnt = get_container(ctx, ctx->last_id, opt);
	cnt->rect = layout_next(ctx);
	if (~opt & OPT_NOFRAME) {
		ctx->draw_frame(ctx, cnt->rect, COLOR_PANELBG);
	}
	push(ctx->container_stack, cnt);
	push_container_body(ctx, cnt, cnt->rect, opt);
	push_clip_rect(ctx, cnt->body);
}


void end_panel(Context *ctx) {
	pop_clip_rect(ctx);
	pop_container(ctx);
}
}